﻿using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Rhino.Mocks;
using XcoAppSpaces.Contracts.Messaging;
using XcoAppSpaces.Contracts.Serialization;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Contracts.Worker;
using XcoAppSpaces.Core.Communication;
using XcoAppSpaces.Core.Communication.WorkerResolve;
using System.Threading;
using XcoAppSpaces.Core.Ports;
using XcoAppSpaces.Core.Ports.Remoting;
using XcoAppSpaces.Core.Ports.Utils;
using XcoAppSpaces.Contracts.Transport;
using XcoAppSpaces.Contracts.Security;
using CausalityContext = XcoAppSpaces.Core.Ports.CausalityContext;

namespace test.XcoAppSpaces.Core.Communication
{
	[TestFixture]
	public class MessageTransmitterTest
	{
		private MockRepository mocks = null;
		private IXcoWorkerRegistry workerRegistryMock1 = null;
		private IXcoWorkerRegistry workerRegistryMock2 = null;
		private IXcoSecurityService securityServiceMock1 = null;
		private IXcoSecurityService securityServiceMock2 = null;
		private int port1 = 0;
		private int port2 = 0;
		private string spaceaddress1 = null;
		private string spaceaddress2 = null;
		
		private PortSet<int, string> testWorkerContract = null;
		private Type testWorkerType = null;
		private string testWorkerName = null;
		private string testWorkerId = null;
		private string causality1 = null;
		private Port<Exception> exceptionPort1 = null;

		private MessageTransmitter conn1 = null;
		private MessageTransmitter conn2 = null;
		private string receivedItem = null;
		private ICausality activeCausality = null;

		private string defaultCommServiceName = "default";

		[SetUp]
		public void SetUp()
		{
			port1 = 8001;
			port2 = 8002;
			spaceaddress1 = Environment.MachineName.ToLower() + ":" + port1;
			spaceaddress2 = Environment.MachineName.ToLower() + ":" + port2;
			testWorkerContract = new PortSet<int, string>();
			testWorkerType = testWorkerContract.GetType();
			testWorkerName = "testworker";
			testWorkerId = testWorkerType.FullName + "/" + testWorkerName;
			causality1 = "c1";
			exceptionPort1 = new Port<Exception>();
			receivedItem = null;
			activeCausality = null;

			mocks = new MockRepository();
			workerRegistryMock1 = mocks.StrictMock<IXcoWorkerRegistry>();
			workerRegistryMock2 = mocks.StrictMock<IXcoWorkerRegistry>();
			securityServiceMock1 = mocks.StrictMock<IXcoSecurityService>();
			securityServiceMock2 = mocks.StrictMock<IXcoSecurityService>();

			//expectations on mocks when instantiating InternalConnectors
			workerRegistryMock1.Register<ResolveWorker>(null, null);
			LastCall.IgnoreArguments();
			workerRegistryMock2.Register<ResolveWorker>(null, null);
			LastCall.IgnoreArguments();
			//TODO expectations on security mocks

			mocks.ReplayAll();

            XcoCommunicationServiceMock mock1 = new XcoCommunicationServiceMock(port1);
            XcoCommunicationServiceMock mock2 = new XcoCommunicationServiceMock(port2);

            mock1.DirectConnect(mock2);
            mock2.DirectConnect(mock1);

			//instantiate and start InternalConnectors
			//Dictionary<string, IXcoTransportService> commService = new Dictionary<string, IXcoTransportService>();
            //commService[defaultCommServiceName] = mock1;
			conn1 = new MessageTransmitter(CreateServiceRegistryMock(workerRegistryMock1, mock1, defaultCommServiceName, new BinarySerializerMock(), securityServiceMock1), new CommunicationEvents());
			//commService = new Dictionary<string, IXcoTransportService>();
            //commService[defaultCommServiceName] = mock2;
			conn2 = new MessageTransmitter(CreateServiceRegistryMock(workerRegistryMock2, mock2, defaultCommServiceName, new BinarySerializerMock(), securityServiceMock2), new CommunicationEvents());
			conn1.Start();
			conn2.Start();
			mocks.BackToRecordAll();
		}

		private IXcoServiceRegistry CreateServiceRegistryMock(IXcoWorkerRegistry localWorkers, IXcoTransportService commService, string defaultCommServiceName, IXcoSerializer serializer, IXcoSecurityService securityService)
		{
			ServiceRegistryMock mock = new ServiceRegistryMock();
			mock.Register(localWorkers);
			mock.Register(new XcoServiceConfiguration
			{
				Service = commService,
				ServiceType = typeof(IXcoTransportService),
				Name = defaultCommServiceName,
				IsDefault = true
			});
			mock.Register(serializer);
			mock.Register(securityService);
			return mock;
		}

		[TearDown]
		public void TearDown()
		{
			try
			{
				conn1.Dispose();
				conn2.Dispose();
			}
			catch (Exception)
			{
			}
			mocks.VerifyAll();
		}

		[Test]
		public void ProcessWorkerMessage()
		{
			string testItem = "teststr";

			//expectations on mocks
			workerRegistryMock2.Exists(testWorkerId, true, false);
			LastCall.Return<bool>(true);
			workerRegistryMock2.Resolve(testWorkerId, true);
			LastCall.Return<IPort>(testWorkerContract);
			securityServiceMock2.CheckPermission(testWorkerContract, testItem, spaceaddress1, Guid.Empty);

			//run test
			mocks.ReplayAll();
			//send message to remote worker
			conn1.SendMessage(new MessageToSend
			{
				PortInfo = new XcoPortInfo(true, spaceaddress2, null, testWorkerId, Guid.Empty),
				PortElement = new PortElement<string> { Item = testItem}
			});//(spaceaddress2, null, null, new WorkerMessage(testWorkerId, Guid.Empty, testItem), null, null));
			Thread.Sleep(1000); //wait for message to be posted
			string receivedItem = testWorkerContract.P1.Receive().Result;
			Assert.AreEqual(testItem, receivedItem);
		}

		[Test]
		public void ProcessWorkerMessageWithCausalities()
		{
			string testItem = "teststr";
			
			//expectations on mocks
			workerRegistryMock2.Exists(testWorkerId, true, false);
			LastCall.Return<bool>(true);
			workerRegistryMock2.Resolve(testWorkerId, true);
			LastCall.Return<IPort>(testWorkerContract);
			securityServiceMock2.CheckPermission(testWorkerContract, testItem, spaceaddress1, Guid.Empty);

			//run test
			mocks.ReplayAll();
			//send message to remote worker
			testWorkerContract.P1.RegisterReceiver((Action<string>) ProcessAndCheckCausality);
			conn1.SendMessage(new MessageToSend
			{
				PortInfo = new XcoPortInfo(true, spaceaddress2, null, testWorkerId, Guid.Empty),
				PortElement = new PortElement<string> { Item = testItem, Causalities = new List<ICausality>{new Causality(exceptionPort1)}}
			}); //(spaceaddress2, null, 
				//new CausalityContext(new[] { new CausalityInfo(new Causality(causality1, exceptionPort1)) }),
				//new WorkerMessage(testWorkerId, Guid.Empty, testItem), null, null));
			Thread.Sleep(1000); //wait for message to be posted
			Assert.AreEqual(testItem, receivedItem);
			Assert.IsNotNull(activeCausality);
			Assert.IsInstanceOf(exceptionPort1.GetType(), activeCausality.ExceptionPort); //only type can be checked (because port has been serialized in between)
		}

		private void ProcessAndCheckCausality(string item)
		{
			receivedItem = item;
			activeCausality = CausalityContext.Current;
				//(Causality)Dispatcher.ActiveCausalities.First();
		}

		//[Test]
		//public void ProcessRemotePortMessage()
		//{
		//    //run test
		//    mocks.ReplayAll();
		//    Port<string> testport = new Port<string>();
		//    Guid portId = conn2.RegisterPort(testport); //RemotePortHelper.GetGuid(testport);
		//    //send message to remote port
		//    string testItem = "teststr";
		//    conn1.Send(spaceaddress2, null, null, new RemotePortMessage(portId, testItem));
		//    Thread.Sleep(1000); //wait for message to be posted
		//    string receivedItem = null;
		//    Assert.IsTrue(testport.Test(out receivedItem));
		//    Assert.AreEqual(testItem, receivedItem);
		//    ////port must not be available after unregistering
		//    //conn2.UnregisterPort(portId);
		//    //conn1.Send(spaceaddress2, null, null, new RemotePortMessage(portId, testItem));
		//    //Thread.Sleep(1000); //wait for message to be posted
		//    //Assert.IsFalse(testport.Test(out receivedItem));
		//}

		//[Test]
		//public void ProcessRemotePortMessageWithCausalities()
		//{
		//    //run test
		//    mocks.ReplayAll();
		//    Port<string> testport = new Port<string>();
		//    Guid portId = conn2.RegisterPort(testport); //conn2.RegisterRemotePort(portId, testport);
		//    //send message to remote port
		//    string testItem = "teststr";
		//    Arbiter.Activate(dpq, Arbiter.Receive(false, testport, ProcessAndCheckCausality));
		//    conn1.Send(spaceaddress2, null, 
		//        new CausalityContext(new List<CausalityInfo> { new CausalityInfo(new Causality(causality1, exceptionPort1)) }), 
		//        new RemotePortMessage(portId, testItem));
		//    Thread.Sleep(1000); //wait for message to be posted
		//    Assert.AreEqual(testItem, receivedItem);
		//    Assert.IsNotNull(activeCausality);
		//    Assert.AreEqual(causality1, activeCausality.Name);
		//    Assert.IsInstanceOfType(exceptionPort1.GetType(), activeCausality.ExceptionPort); //only type can be checked (because port has been serialized in between)
		//}

		//[Test]
		//public void ProcessRemotePortMessageDeserializationError()
		//{
		//    //run test
		//    mocks.ReplayAll();
		//    Port<string> testport = new Port<string>();
		//    Guid portId = conn2.RegisterPort(testport); //conn2.RegisterRemotePort(portId, testport);
		//    //send message to remote port
		//    Arbiter.Activate(dpq, Arbiter.Receive(false, testport, ProcessAndCheckCausality));
		//    conn1.Send(spaceaddress2, null,
		//        new CausalityContext(new List<CausalityInfo> { new CausalityInfo(new Causality(causality1, exceptionPort1)) }),
		//        new SerializationErrorMessage());
		//    Thread.Sleep(1000); //wait for message to be posted
		//    Assert.IsNull(receivedItem);
		//    Assert.AreEqual(1, exceptionPort1.ItemCount);
		//    Exception ex = exceptionPort1;
		//    Assert.IsInstanceOfType(typeof(XcoSerializationException), ex);
		//}

		//[Test]
		//public void RegisterPort()
		//{
		//    mocks.ReplayAll();
		//    Port<string> testPort = new Port<string>();
		//    Guid guid1 = conn1.RegisterPort(testPort);
		//    Guid guid2 = conn1.RegisterPort(testPort);
		//    Assert.AreNotEqual(Guid.Empty, guid1);
		//    Assert.AreEqual(guid1, guid2);
		//}

		//[Test]
		//public void AutomaticUnregisterPortAfterGarbageCollection()
		//{
		//    mocks.ReplayAll();
		//    Dictionary<Guid, WeakReference> registeredPorts = (Dictionary<Guid, WeakReference>)conn1.GetPrivateField("registeredPorts");
		//    int countNoRegisteredPorts = registeredPorts.Count;
		//    Guid guid1 = conn1.RegisterPort(new Port<string>());
		//    Guid guid2 = conn1.RegisterPort(new Port<int>());
		//    GC.Collect(); //registered ports should be garbage collected
		//    Assert.AreEqual(countNoRegisteredPorts + 2, registeredPorts.Count);
		//    Assert.IsFalse(registeredPorts[guid1].IsAlive);
		//    Assert.IsFalse(registeredPorts[guid2].IsAlive);
		//    conn1.CallPrivateFunction("CheckPortRegistry", new object()); //check should remove garbage collected ports
		//    Assert.AreEqual(countNoRegisteredPorts, registeredPorts.Count);
		//}

		//[Test]
		//public void AutomaticUnregisterPortAfterGarbageCollection_WaitForTask()
		//{
		//    mocks.ReplayAll();
		//    Dictionary<Guid, WeakReference> registeredPorts = (Dictionary<Guid, WeakReference>)conn1.GetPrivateField("registeredPorts");
		//    int countNoRegisteredPorts = registeredPorts.Count;
		//    Guid guid1 = conn1.RegisterPort(new Port<string>());
		//    Guid guid2 = conn1.RegisterPort(new Port<int>());
		//    GC.Collect(); //registered port should be garbage collected
		//    Assert.AreEqual(countNoRegisteredPorts + 2, registeredPorts.Count);
		//    Assert.IsFalse(registeredPorts[guid1].IsAlive);
		//    Assert.IsFalse(registeredPorts[guid2].IsAlive);
		//    Thread.Sleep(20500); //automatic check task should run here and remove the garbage collected ports
		//    Assert.AreEqual(countNoRegisteredPorts, registeredPorts.Count);
		//}

		[Test]
		public void CommServiceInfo()
		{
			mocks.ReplayAll();
			Assert.AreEqual(1, conn1.CommServiceInfo.Count());
			Assert.AreEqual(defaultCommServiceName, conn1.CommServiceInfo.First().Name);
			Assert.IsTrue(conn1.CommServiceInfo.First().IsDefault);
            Assert.IsInstanceOf(typeof(XcoCommunicationServiceMock), conn1.CommServiceInfo.First().Service);
		}
	}
}
